import time
import sqlite3
import numpy as np
import multiprocessing
from multiprocessing import Queue
from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal
from PyQt5.Qt import QThread, QApplication, QPixmap, QImage, QShortcut
from PyQt5.QtGui import QIcon
from common.utils.NovishLogger import logger
from common.utils.design_pattern.observe_pattern import Observer
from .windows.ui_main import UiMain
from .windows.ui_progress import UiProgress
from .windows.ui_base_databoard import UiBaseDataBoard
from .windows.ui_base_datareview import UiBaseDateReview
from ._config import UI_RES_PATH

__doc__ = """ GUI 基类
- 请勿直接修改本模块内容
- 相应操作请在其派生类[app.py]中实现
"""


class BaseApp(UiMain):
    def __init__(self, window_title='Novish', ipc_dict=None, cam_matrix=(1, 1)):
        super(BaseApp, self).__init__(cam_matrix)
        self.ipc_dict = ipc_dict
        self.paint_thread = EmitThread()
        self.ui_queue = self.paint_thread.queue
        self.setWindowTitle(window_title)
        self.window_title = window_title
        self.window_icon = QIcon(str(UI_RES_PATH / 'logo.png'))
        self.run_flag = False  # 是否正在运行
        self.piece_cnt = 0  # 工件号
        self.db_conf = {  # 数据库基础信息
            'path': None,  # str
            'tables': None,  # dict
        }

        # 控制UI数据的显示 搭配setattr(self, k, v)使用效果更佳
        self.cam_matrix: tuple  # 相机画面矩阵(row, column)
        self.ui_dock_photos: int  # 检测详情[左侧dock]中的图片数量
        self.ui_final_flag: str  # 综合判定[OK/NG]
        self.ui_det_time: str  # 检测时间[ms]
        self.paint: tuple  # 绘制相机画面(id, img)

        # 子界面
        self.win_progress = UiProgress()  # 进度条
        self.observer_list.append(self.win_progress)
        # self.win_settings = UiBaseSettings()  # 设置页
        self.win_databoard = UiBaseDataBoard()  # 数据展示
        self.win_datareview = UiBaseDateReview()  # 数据展示
        # self.win_camboard = UiBaseSettings(6)  # 相机调试
        # self.win_controlboard = UiBaseSettings(7)  # 检测控制

        self.__connect()

    def showEvent(self, event):
        self.SetWindowCentered()
        self._db_init()
        self._det_control()
        self.paint_thread.start()
        if self.win_progress:
            self.win_progress.show()
            self.setEnabled(False)

    def __connect(self):
        """ 事件 """
        if hasattr(self, 'BTN_CONTROL'):
            self.BTN_CONTROL.clicked.connect(self._control)
        if hasattr(self, 'BTN_SET'):
            self.BTN_SET.clicked.connect(self._setting)
        QShortcut('F5', self).activated.connect(self._control)
        QShortcut('F9', self).activated.connect(self._setting)

        self.action_data.triggered.connect(lambda: self._show_subwin(self.win_databoard))
        self.action_review.triggered.connect(lambda: self._show_subwin(self.win_datareview))
        self.action_cam.triggered.connect(lambda: self._show_subwin(self.win_camboard))
        self.action_control.triggered.connect(lambda: self._show_subwin(self.win_controlboard))
        if hasattr(self, 'action_settings'):
            self.action_settings.triggered.connect(self._setting)
        self.paint_thread.qt_signal.connect(self._update_system_status)
        # self.paint_thread.qt_signal.connect(self._update)

        self.win_progress.sig.connect(self._load_result)

    def _update(self, data):
        """
        更新UI,

        参数:
        - data: 包含UI元素及其新值的字典

        该方法遍历数据字典，尝试更新UI元素的值
        如果UI元素不存在，则打印错误信息
        如果UI元素是PyQt5的组件，则调用SetValue方法更新值
        否则，直接通过setattr更新UI元素的值
        """
        for k, v in data.items():
            logger.debug(f"_update k:{k}")
            o = getattr(self, k, 'badkey')
            if o == 'badkey':
                print('Error:', 'UI中未找到此KEY -->', k)
            elif getattr(o, '__module__', None) == 'PyQt5.QtWidgets':
                self.SetValue(o, v)
            else:
                setattr(self, k, v)

    def _run(self):
        """ 间隙 """
        self.piece_cnt += 1
        print('当前工件号', self.piece_cnt)
        self._det_control()
        self.run()

    def _det_control(self):
        """ 通过阈值控制相应检测功能的启停 """
        keys = [item.replace('_CTRL_STATUS', '') for item in self.ipc_dict.keys() if item.endswith('_CTRL_STATUS')]
        self.det_info = data = {}
        # 计数/定时可同时开启或关闭
        for name in keys:
            data.update({name: {}.fromkeys(['flag', 'time', 'piece'], None)})
            if self.ipc_dict[f'{name}_TIME']:
                begin = self.ipc_dict[f'{name}_TIME_BEGIN']
                end = self.ipc_dict[f'{name}_TIME_END']
                flag = True if begin <= time.time() <= end else False
                data[name]['time'] = (begin, end)
            if self.ipc_dict[f'{name}_NUM']:
                begin = self.ipc_dict[f'{name}_NUM_BEGIN']
                end = self.ipc_dict[f'{name}_NUM_END']
                flag = True if begin <= self.piece_cnt <= end else False
                data[name]['piece'] = (begin, end)
            if not self.ipc_dict[f'{name}_TIME'] and not self.ipc_dict[f'{name}_NUM']:
                flag = True if self.ipc_dict[f'{name}_CTRL_STATUS'] == '开启' else False
            status = self.ipc_dict[f'{name}_CTRL_STATUS']

            # [启用]状态为开启且计数/定时在设定范围内
            # [停用]状态为关闭且计数/定时在设定范围内
            if (status == '开启' and flag) or (status == '关闭' and flag):
                self.ipc_dict[name.replace('UI_KEY_', '')] = True
                data[name]['flag'] = True
                # print(name, '启用')
            else:
                self.ipc_dict[name.replace('UI_KEY_', '')] = False
                data[name]['flag'] = False
                # print(name, '停用')

    def _det_ficon(self, icons):
        icons = list(set(icons))
        icon = icons[0] if len(icons) == 1 else str(UI_RES_PATH / 'status_yellow.png')
        return icon

    def _det_fdata(self, v):
        def _ftime(ts):
            return tuple(time.strftime('%Y年%m月%d日 %H:%M:%S', time.localtime(t)) for t in ts)

        icon = str(UI_RES_PATH / 'status_green.png') if v['flag'] else str(UI_RES_PATH / 'status_red.png')
        tip = ['当前状态： 开启'] if v['flag'] else ['当前状态： 关闭']
        tip.append('工件号： 第%s个 - 第%s个' % v['piece']) if v['piece'] else None
        tip.append('时间： %s - %s' % _ftime(v['time'])) if v['time'] else None
        tip = '\n'.join(tip)
        return icon, tip

    def _db_init(self):
        """ 数据库初始化 """
        if not self.db_conf['path']:
            return
        conn = sqlite3.connect(self.db_conf['path'])
        for sql in self.db_conf['tables'].values():
            try:
                conn.execute(sql)
            except Exception:
                raise
        conn.commit()
        conn.close()

    def _paint(self, id_data):
        """
        绘制相机画面

        Args:
            id_data: tuple(cam_id, img)

        ### Note
        - 仅支持RGB 其他类型需在派生类重写此方法
        """
        cam_id, data = id_data
        try:
            if isinstance(data, str):
                self.__dict__[f'cam{cam_id:d}'].setText(data)
                self.__dict__[f'cam{cam_id:d}'].img = None
                self.__dict__[f'cam{cam_id:d}'].update()
            elif isinstance(data, np.ndarray):
                h, w, depth = data.shape
                self.__dict__[f'cam{cam_id:d}'].PutPixmap(
                    QPixmap(QImage(data.data, w, h, w * depth, QImage.Format_RGB888)))
        except Exception as paint_err:
            print('painting error', paint_err)

    @pyqtSlot(bool)
    def _load_result(self, flag):
        """ 进度条加载结果 """
        if flag:
            self.setEnabled(True)
        else:
            pass

    @pyqtSlot()
    def _control(self):
        """ 运行按钮 """
        if self.run_flag:
            txt = '开始运行'
            self.run_flag = False
        else:
            txt = '停止运行'
            self.run_flag = True

        # print(self.BTN_CONTROL.text())
        self.BTN_CONTROL.setText(txt)

    @pyqtSlot()
    def _setting(self):
        """ 设置按钮  """
        if self.win_settings_dialog:
            print('打开设置')
            self.win_settings_dialog.show()
        else:
            print('此页面已被移除')

    @pyqtSlot(object)
    def _show_subwin(self, obj):
        if not obj.isVisible():
            obj.show()
        else:
            obj.activateWindow()

    def run(self):
        """ 间隙 """
        pass

    @property
    def paint(self):
        return '绘制相机画面'

    @paint.setter
    def paint(self, v):
        self._paint(v)

    @property
    def load_result(self):
        """ 程序加载结果 """
        return self.win_progress.load_result

    @load_result.setter
    def load_result(self, v):
        if v == True:
            self.win_progress.load_result = True
        elif isinstance(v, str):
            QtWidgets.QMessageBox.information(self, '提示', v)

    @property
    def rtu_status(self):
        return 'modbus rtu 初始化状态'

    @rtu_status.setter
    def rtu_status(self, v):
        if isinstance(v, str):
            QtWidgets.QMessageBox.information(self, '提示', v)

    @property
    def MODEL_USAGE(self):
        """ 模型开关 """
        return self.win_settings.ui_model_usage

    @MODEL_USAGE.setter
    def MODEL_USAGE(self, v: list):
        self.win_settings.ui_model_usage = v

    @property
    def CAM_SN(self):
        """ 相机SN """
        return self.win_settings.ui_cam_sn

    @CAM_SN.setter
    def CAM_SN(self, v: list):
        self.win_settings.ui_cam_sn = v

    def _update_system_status(self, data):
        """
        Update system status, the observable system status will notify observers(UI widgets) to update.
        @param data:
        @return:
        """
        self.system_status.set_system_status(data)


class EmitThread(QThread):
    """
    发送信号的线程类，继承自QThread。
    该类用于在后台线程中处理数据，并通过信号将处理结果传递给GUI组件。
    """
    # 定义一个Python信号量，可以携带字典类型的数据
    qt_signal = pyqtSignal(dict)

    def __init__(self):
        """
        初始化函数。
        创建一个队列用于存储待发送的数据，并将线程初始化。
        """
        super().__init__()
        self.queue = Queue()

    def run(self):
        """
        线程的入口函数。
        无限循环，从队列中获取数据，如果数据为字典类型，则通过信号发送出去；
        如果数据为空或类型不正确，则记录警告信息。
        """
        while True:
            data = self.queue.get()
            if data is not None:
                if isinstance(data, dict):
                    # 如果从队列中获取的数据为字典类型，则通过信号发送出去
                    self.qt_signal.emit(data)
                else:
                    # 如果数据类型不正确，记录警告信息
                    logger.warning('UI接受到的数据应为字典类型', f'当前数据类型为{type(data)}')
            else:
                # 如果从队列中获取的数据为空，记录警告信息
                logger.warning('UI接受到的数据为空')


if __name__ == '__main__':
    import sys
    from PyQt5.Qt import QGuiApplication

    QGuiApplication.setAttribute(Qt.AA_DisableHighDpiScaling)

    app = QApplication(sys.argv)

    win = BaseApp()
    win.show()

    sys.exit(app.exec_())
